﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using MovingChame.Web.Helpers;
using MovingChame.Web.Models;
using System.Data.Spatial;

namespace MovingChame.Web.Infrastructure
{
    public class MovingChameMembershipProvider : MembershipProvider
    {
        private string connectionStringName;

        private string providerName;

        MovingChameModelContainer db = null;

        private string applicationName;
        public override string ApplicationName
        {
            get { return applicationName; }
            set { applicationName = value; }
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);

            providerName = name;

            if (config["applicationName"] != null)
                applicationName = config["applicationName"];

            if (config["connectionStringName"] != null)
            {
                connectionStringName = config["connectionStringName"];
                db = new MovingChameModelContainer();
                db.Database.Connection.ConnectionString = ConfigurationHelper.GetConnectionString(connectionStringName);
                db.Database.Connection.Open();
            }

            if (config["enablePasswordRetrieval"] != null)
                enablePasswordRetrieval = bool.Parse(config["enablePasswordRetrieval"]);

            if (config["enablePasswordReset"] != null)
                enablePasswordReset = bool.Parse(config["enablePasswordReset"]);

            if (config["requiresQuestionAndAnswer"] != null)
                requiresQuestionAndAnswer = bool.Parse(config["requiresQuestionAndAnswer"]);

            if (config["requiresUniqueEmail"] != null)
                requiresUniqueEmail = bool.Parse(config["requiresUniqueEmail"]);

            if (config["maxInvalidPasswordAttempts"] != null)
                maxInvalidPasswordAttempts = int.Parse(config["maxInvalidPasswordAttempts"]);

            if (config["minRequiredPasswordLength"] != null)
                minRequiredPasswordLength = int.Parse(config["minRequiredPasswordLength"]);

            if (config["minRequiredNonAlphanumericCharacters"] != null)
                minRequiredNonAlphanumericCharacters = int.Parse(config["minRequiredNonAlphanumericCharacters"]);

            if (config["passwordAttemptWindow"] != null)
                passwordAttemptWindow = int.Parse(config["passwordAttemptWindow"]);

            // Add default admin account
            if (db.Users.SingleOrDefault(o => o.Username == Constants.DefaultAdminUsername) == null)
            {
                MembershipCreateStatus status;
                var user = CreateUser(Constants.DefaultAdminUsername, Constants.DefaultAdminPassword, Constants.DefaultAdminEmail,
                    null, null, true, Guid.NewGuid(), out status);
                Roles.AddUserToRole(Constants.DefaultAdminUsername, Constants.RoleAdmin);
            }
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion,
            string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            // Validate username
            if (string.IsNullOrEmpty(username))
            {
                status = MembershipCreateStatus.InvalidUserName;
                return null;
            }

            if (GetUser(username, false) != null)
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }

            // Validate password
            var args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (string.IsNullOrEmpty(password))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            // Validate email
            if (string.IsNullOrEmpty(email) || !EmailHelper.ValidateEmail(email))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            if (RequiresUniqueEmail && !string.IsNullOrEmpty(GetUserNameByEmail(email)))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            // Validate question and answer
            if (RequiresQuestionAndAnswer && passwordQuestion == null)
            {
                status = MembershipCreateStatus.InvalidQuestion;
                return null;
            }

            if (RequiresQuestionAndAnswer && passwordAnswer == null)
            {
                status = MembershipCreateStatus.InvalidAnswer;
                return null;
            }

            // Save User
            MembershipUser user = GetUser(username, true);

            if (user == null)
            {
                // Add new user
                var newUser = new User
                {
                    Username = username,
                    Password = SecurityHelper.GetHash(password),
                    Email = email,
                    PasswordQuestion = passwordQuestion,
                    PasswordAnswer = passwordAnswer,
                    IsApproved = isApproved,
                    IsLockedOut = false,
                    ProviderUserKey = (Guid)providerUserKey,
                    CreationDateTime = DateTime.UtcNow,
                    LastActivityDateTime = DateTime.UtcNow,
                    LastLoginDateTime = DateTime.UtcNow,
                    LastUpdateDateTime = DateTime.UtcNow,
                    LastLockoutDateTime = DateTime.UtcNow,
                    LastPasswordChangedDateTime = DateTime.UtcNow
                };
                db.Users.Add(newUser);
                db.SaveChanges();

                Roles.AddUserToRole(username, Constants.DefaultRole);
                status = MembershipCreateStatus.Success;
                return GetUser(username, true);
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }

            return null;
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        private bool enablePasswordReset = false;
        public override bool EnablePasswordReset
        {
            get { return enablePasswordReset; }
        }

        private bool enablePasswordRetrieval = false;
        public override bool EnablePasswordRetrieval
        {
            get { return enablePasswordRetrieval; }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            var users = db.Users.Where(o => o.Email.Contains(emailToMatch));
            totalRecords = users.Count();

            var usersPage = users.Skip(pageSize * pageIndex).Take(pageSize);

            var result = new MembershipUserCollection();
            foreach (var user in usersPage)
            {
                result.Add(GetUser(user.Username, false));
            }

            return result;
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            var users = db.Users.Where(o => o.Username.Contains(usernameToMatch));
            totalRecords = users.Count();

            var usersPage = users.Skip(pageSize * pageIndex).Take(pageSize);

            var result = new MembershipUserCollection();
            foreach (var user in usersPage)
            {
                result.Add(GetUser(user.Username, false));
            }

            return result;
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = db.Users.Count();
            var usersPage = db.Users.Skip(pageSize * pageIndex).Take(pageSize);
            var result = new MembershipUserCollection();

            foreach (var user in usersPage)
            {
                result.Add(GetUser(user.Username, false));
            }

            return result;
        }

        public override int GetNumberOfUsersOnline()
        {
            return db.Users.Where(o => DateTime.UtcNow.Subtract(o.LastActivityDateTime) < Constants.OnlineActivityTimeThreshold).Count();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new InvalidOperationException("Password Retrieval is not enabled!");
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            var user = db.Users.SingleOrDefault(u => u.Username == username);

            if (user == null)
                return null;

            if (userIsOnline)
            {
                user.LastActivityDateTime = DateTime.UtcNow;
                db.SaveChanges();
            }

            var membership = CreateMembershipUser(user);

            return membership;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            var user = db.Users.SingleOrDefault(o => o.ProviderUserKey == (Guid)providerUserKey);

            if (user == null)
                return null;

            if (userIsOnline)
            {
                user.LastActivityDateTime = DateTime.UtcNow;
                db.SaveChanges();
            }

            var membership = CreateMembershipUser(user);

            return membership;
        }

        public override string GetUserNameByEmail(string email)
        {
            var user = db.Users.SingleOrDefault(o => o.Email == email);

            if (user == null)
                return null;

            return user.Username;
        }

        private int maxInvalidPasswordAttempts = 5;
        public override int MaxInvalidPasswordAttempts
        {
            get { return maxInvalidPasswordAttempts; }
        }

        private int minRequiredNonAlphanumericCharacters = 0;
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return minRequiredNonAlphanumericCharacters; }
        }

        private int minRequiredPasswordLength = 6;
        public override int MinRequiredPasswordLength
        {
            get { return minRequiredPasswordLength; }
        }

        private int passwordAttemptWindow = 5;
        public override int PasswordAttemptWindow
        {
            get { return passwordAttemptWindow; }
        }

        private MembershipPasswordFormat passwordFormat;
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        private string passwordStrengthRegularExpression;
        public override string PasswordStrengthRegularExpression
        {
            get { return passwordStrengthRegularExpression; }
        }

        private bool requiresQuestionAndAnswer = false;
        public override bool RequiresQuestionAndAnswer
        {
            get { return requiresQuestionAndAnswer; }
        }

        private bool requiresUniqueEmail = true;
        public override bool RequiresUniqueEmail
        {
            get { return requiresUniqueEmail; }
        }

        public override bool UnlockUser(string userName)
        {
            var user = db.Users.SingleOrDefault(o => o.Username == userName);

            if (user == null)
                return false;

            user.IsLockedOut = false;
            db.SaveChanges();

            return true;
        }

        public override void UpdateUser(MembershipUser user)
        {
            var oldUser = db.Users.SingleOrDefault(o => o.Username == user.UserName);

            if (oldUser == null)
                return;

            oldUser.Email = user.Email;
            oldUser.IsApproved = user.IsApproved;
            oldUser.IsLockedOut = user.IsLockedOut;
            oldUser.CreationDateTime = user.CreationDate;
            oldUser.LastLoginDateTime = user.LastLoginDate;
            oldUser.LastActivityDateTime = user.LastActivityDate;
            oldUser.LastPasswordChangedDateTime = user.LastPasswordChangedDate;
            oldUser.LastLockoutDateTime = user.LastLockoutDate;

            db.SaveChanges();
        }

        public override bool ValidateUser(string username, string password)
        {
            var user = db.Users.SingleOrDefault(o => o.Username == username);

            if (user == null)
                return false;

            if (user.Password != SecurityHelper.GetHash(password))
                return false;

            user.LastLoginDateTime = DateTime.UtcNow;
            user.LastActivityDateTime = DateTime.UtcNow;
            db.SaveChanges();

            return true;
        }

        protected override void OnValidatingPassword(ValidatePasswordEventArgs e)
        {
            base.OnValidatingPassword(e);

            if (e.Password.Length < this.MinRequiredPasswordLength)
            {
                e.Cancel = true;
                e.FailureInformation = new ArgumentException("Password is too short", "Password");
                return;
            }
        }

        private MembershipUser CreateMembershipUser(User user)
        {
            var result = new MembershipUser(
                providerName,
                user.Username,
                user.ProviderUserKey,
                user.Email,
                user.PasswordQuestion,
                user.PasswordAnswer,
                user.IsApproved,
                user.IsLockedOut,
                user.CreationDateTime,
                user.LastLoginDateTime,
                user.LastActivityDateTime,
                user.LastPasswordChangedDateTime,
                user.LastLockoutDateTime
                );
            return result;
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }
    }
}